#pragma once

#include <cstddef>
#include <memory>
#include <vector>

#include "util/algo/lock.h"

namespace hawking {
namespace indexlib {

// suit for building segment inverted index's term's posting which may continue adding,
// for its one write + multi read.
template <typename T, typename Alloc = std::allocator<T>>
class SnapshotVector {
public:
    class Iterator {
    public:
        Iterator(std::shared_ptr<std::vector<T, Alloc>> snapshot, size_t sz) :
            snapshot_(snapshot), size_(sz) {}
        bool HasNext() const { return cursor_ < size_; }
        const T& Next() { return snapshot_->operator[](cursor_++); }
        size_t Size() const { return size_; }

    private:
        std::shared_ptr<std::vector<T, Alloc>> snapshot_;
        size_t cursor_ = 0;
        size_t size_ = 0;
    };

public:
    SnapshotVector()
        : allocator_()
        , vector_(std::make_shared<std::vector<T, Alloc>>(allocator_)) {
        Init();
    }
    SnapshotVector(Alloc allocator)
        : allocator_(allocator)
        , vector_(std::make_shared<std::vector<T, Alloc>>(allocator_)) {
        Init();
    }

    void Init() {
        vector_->resize(4);
        current_capacity_ = 4;
        data_ = vector_->data();
    }

    void PushBack(T&& item) {
        if (current_size_ >= current_capacity_) {
            Reallocate_();
        }
        data_[current_size_++] = std::forward<T>(item);
    }

    void PushBack(const T& item) {
        if (current_size_ >= current_capacity_) {
            Reallocate_();
        }
        data_[current_size_++] = item;
    }

    size_t Size() const { return current_size_; }

    Iterator Snapshot() { return Iterator(Current_(), current_size_); }

    T* Data() {
        return data_;
    }

private:
    std::shared_ptr<std::vector<T, Alloc>> Current_() {
        ScopedSpinLock lock(mutex_);
        return vector_;
    }

    void Reallocate_();

private:
    Alloc allocator_;
    T* data_ = nullptr;
    size_t current_capacity_ = 0;
    size_t current_size_ = 0;
    std::shared_ptr<std::vector<T, Alloc>> vector_;
    SpinLock mutex_;
};

template<class T, class Alloc>
void SnapshotVector<T, Alloc>::Reallocate_() {
    std::shared_ptr<std::vector<T, Alloc>> target =
        std::make_shared<std::vector<T, Alloc>>(allocator_);
    target->resize(vector_->size() * 2);
    for (size_t i = 0; i < vector_->size(); ++i) {
        target->operator[](i) = vector_->operator[](i);
    }
    current_capacity_ = target->capacity();
    ScopedSpinLock lock(mutex_);
    vector_ = target;
    data_ = vector_->data();
}

}
}